# write by hashaki first edit on 20181026  last change on 20181122
# after backtest and forward real trade,simulate engine used to use the strategy
# from backtest to close the real trade envirenment.
# different form the backtest,simulate add the order judgement function
import shelve
import os
import sys
from collections import OrderedDict
from copy import copy
import traceback
import psutil
from datetime import datetime
from time import sleep

from hashakisea.engine.brokers.simulateBroker import SimulationBroker
from hashakisea.constant.objectClass import barClass,tradeClass,orderClass,StopOrder,ResultClass,tickClass
from hashakisea.constant.chinese import (DIRECTION_LONG,DIRECTION_SHORT,STOPORDER_TRIGGERED,
STATUS_ALLTRADED,STATUS_NOTTRADED,STATUS_CANCELLED,CTAORDER_BUY,OFFSET_OPEN,CTAORDER_SELL,OFFSET_CLOSE,
CTAORDER_SHORT,CTAORDER_COVER)

class SimulateEngine:
    def __init__(self,eventEngine):
        self.eventEngine=eventEngine
        self.gateway=None
        self.strategy=None
        self.__broker=SimulationBroker(self)
        self.bar=barClass()
        self.tick=tickClass()
        self.__activate=False
        self.subscrib_target_symbol=''
        # 事件引擎跑起来
        self.eventEngine.start()

        # 记录当前日期
        self.todayDate=datetime.now().strftime('%Y%m%d')


        self.workingLimitOrderDict = OrderedDict()  # 活动限价单字典，用于进行撮合用
        self.limitOrderDict = OrderedDict()         # 限价单字典
        self.workingStopOrderDict=dict()           # 停止单撤销后会从本字典中删除
        self.limitOrderCount=0
        self.tradeDict={}
        self.tradeCount=0
        self.pos=0.0
        self.result=ResultClass()
        self.priceTick=0.0
        self.resultList=[]
        self.stopOrderCount=0
        self.cashList=[]
        self.capitalList=[]
        self.posList=[]
        self.depositList=[]
        self.deposit=0.0
        self.marketValueList=[]
        # 本地停止单字典
        # key为stopOrderID，value为stopOrder对象
        self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除

        self.mode='TICK'
        self.dt=''
        self.counter_tick=0
        self.tickList=[]
        self.first_tick_time=''

        # 账户信息
        self.capital=None
        self.marketMoney=0.0
        self.cash=0.0

    @property
    def broker(self):
        return self.__broker
    
    def addQueue(self,q):
        '''添加外部的队列'''
        self.q=q

    def addGateway(self,gateway):
        '''添加gateway'''
        self.gateway=gateway

    def addStrategy(self,strategy):
        '''添加策略'''
        self.strategy=strategy

    def sendOrder(self,taget_symbol,type_,price,volume,strategy_name):
        '''发单'''
        self.limitOrderCount += 1
        orderID = str(self.limitOrderCount)

        order = orderClass()
        order.taget_symbol= taget_symbol
        order.price = self.roundToPriceTick(price)
        order.status = STATUS_NOTTRADED     # 刚提交尚未成交
        order.orderID = orderID
        order.HaOrderID = orderID
        order.orderTime = str(self.dt)

        if type_== CTAORDER_BUY:
            order.direction = DIRECTION_LONG
            order.totalVolume = volume

        elif type_== CTAORDER_SELL:
            order.direction = DIRECTION_SHORT
            order.totalVolume = volume

        elif type_== CTAORDER_SHORT:
            order.direction = DIRECTION_SHORT
            order.totalVolume = volume

        elif type_== CTAORDER_COVER:
            order.direction = DIRECTION_LONG
            order.totalVolume = volume

        else:
            raise ValueError("不是正确的type：{} 0为buy，1为sell，2为撤单".format(type_))

        key = u'{}.{}'.format(strategy_name,orderID)
        # 保存到限价单字典中
        self.workingLimitOrderDict[key] = order
        self.limitOrderDict[key] = order
        return key
    
    def sendStopOrder(self, taget_symbol, orderType, price, volume, strategy):
        """发停止单（本地实现）"""
        self.stopOrderCount += 1
        stopOrderID ='stopOrder' + str(self.stopOrderCount)

        so = StopOrder()
        so.target_symbol = taget_symbol
        so.price = self.roundToPriceTick(price)
        so.volume = volume
        so.strategy = strategy
        so.stopOrderID = stopOrderID
        so.status = '等待中'

        if orderType == CTAORDER_BUY:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SHORT:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_CLOSE           

        # 保存stopOrder对象到字典中
        self.stopOrderDict[stopOrderID] = so
        self.workingStopOrderDict[stopOrderID] = so

        return stopOrderID

    def cancelOrder(self,HaOrderID=None):
        '''取消订单'''
        # 撤单
        if HaOrderID in self.workingLimitOrderDict:
            order = self.workingLimitOrderDict[HaOrderID]
            order.status = STATUS_CANCELLED
            order.cancelTime = str(self.dt)
            del self.workingLimitOrderDict[HaOrderID]
        # 撤销所有单，TODO：做成按品种的全撤销
        # Symbol参数:指定合约的撤单；
        # OFFSET参数:指定Offset的撤单,缺省不填写时，为所有
        else:
            for OrderID_ in self.workingLimitOrderDict.keys():
                order = self.workingLimitOrderDict[OrderID_]
                order.status = STATUS_CANCELLED
                order.cancelTime = str(self.dt)
                del self.workingLimitOrderDict[OrderID_]
    
    def crossStopOrder(self):
        """基于最新数据撮合停止单"""
        # 先确定会撮合成交的价格，这里和限价单规则相反
        if self.mode == 'BAR':
            buyCrossPrice = self.bar.high    # 若买入方向停止单价格低于该价格，则会成交
            sellCrossPrice = self.bar.low    # 若卖出方向限价单价格高于该价格，则会成交
            bestCrossPrice = self.bar.open   # 最优成交价，买入停止单不能低于，卖出停止单不能高于
            target_symbol = self.bar.target_symbol

        else:
            buyCrossPrice = self.tick.lastPrice
            sellCrossPrice = self.tick.lastPrice
            bestCrossPrice = self.tick.lastPrice
            target_symbol = self.tick.target_symbol 

        # 遍历停止单字典中的所有停止单
        for stopOrderID, so in list(self.workingStopOrderDict.items()):     # self.workingStopOrderDict.items()转换成list结构，后下面注释能完成
           
            buyCross = so.direction == DIRECTION_LONG and so.price <= buyCrossPrice #and vtSymbol.lower() == so.vtSymbol.lower()
            sellCross = so.direction == DIRECTION_SHORT and so.price >= sellCrossPrice #and vtSymbol.lower() == so.vtSymbol.lower()

            # 如果发生了成交 
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount += 1            # 成交编号自增1

                # 添加result,如果没有持仓，则说明是一个新的交易对的开始
                if self.pos==0:
                    self.result.open_time=self.dt

                tradeID = str(self.tradeCount)
                trade = tradeClass()
                trade.target_symbol = so.target_symbol
                trade.tradeID = tradeID
                trade.HaTradeID = tradeID
                
                if buyCross:
                    trade.price = max(bestCrossPrice, so.price)
                    self.pos+=so.volume

                    # 为了计算交易对结果
                    self.result.open_base.append(trade.price)
                    self.result.open_vol.append(so.volume)
                else:
                    trade.price = min(bestCrossPrice, so.price)
                    self.pos-=so.volume

                    self.result.close_base.append(trade.price)
                    self.result.close_vol.append(so.volume)
                    # 卖出后仓位为0,说明是一个交易对的结束
                    if self.pos==0:
                        # TODO:为了防止报错在这里加了slippage，commis，pnl，以后在引擎中赋值即可删掉
                        self.result.commis=0.01
                        self.result.pnl=10.0
                        self.result.slippage=0.1

                        self.result.clsoe_time=self.dt
                        self.resultList.append(self.result)
                        self.restartResult(self.result)
                
                self.limitOrderCount += 1
                orderID = str(self.limitOrderCount)
                trade.orderID = orderID
                trade.HaOrderID = orderID
                
                trade.direction = so.direction
                trade.offset = so.offset
                trade.volume = so.volume
                trade.tradeTime = str(self.dt)
                trade.dt = self.dt
                self.strategy.onTrade(trade)
                
                self.tradeDict[tradeID] = trade
                
                # 推送委托数据
                so.status = STOPORDER_TRIGGERED

                order = orderClass()
                order.target_symbol = so.target_symbol
                order.symbol = so.target_symbol
                order.orderID = orderID
                order.HaOrderID = orderID
                order.direction = so.direction
                order.offset = so.offset
                order.price = so.price
                order.totalVolume = so.volume
                order.tradedVolume = so.volume
                order.status = STATUS_ALLTRADED
                order.orderTime = trade.tradeTime
                self.strategy.onOrder(order)
                
                self.limitOrderDict[orderID] = order
                
                # 从字典中删除该限价单
                if stopOrderID in self.workingStopOrderDict:
                    del self.workingStopOrderDict[stopOrderID]

    def crossLimitOrder(self):
        """基于最新数据撮合限价单"""
        if self.mode=='BAR':
            buyCrossPrice = self.roundToPriceTick(self.bar.low) + self.priceTick        # 若买入方向限价单价格高于该价格，则会成交
            sellCrossPrice = self.roundToPriceTick(self.bar.high) - self.priceTick      # 若卖出方向限价单价格低于该价格，则会成交
            buyBestCrossPrice = self.roundToPriceTick(self.bar.open) + self.priceTick   # 在当前时间点前发出的买入委托可能的最优成交价
            sellBestCrossPrice = self.roundToPriceTick(self.bar.open) - self.priceTick  # 在当前时间点前发出的卖出委托可能的最优成交价
            target_symbol = self.bar.target_symbol
        else:
            
            buyCrossPrice = self.tick.askPrice1
            sellCrossPrice = self.tick.bidPrice1
            buyBestCrossPrice = self.tick.askPrice1
            sellBestCrossPrice = self.tick.bidPrice1
            target_symbol = self.tick.target_symbol
            

        # 遍历限价单字典中的所有限价单
        for orderID, order in list(self.workingLimitOrderDict.items()):
            # 判断是否会成交
            buyCross = order.direction == DIRECTION_LONG and order.price >= buyCrossPrice #and vtSymbol.lower() == order.vtSymbol.lower()
            sellCross = order.direction == DIRECTION_SHORT and order.price <= sellCrossPrice #and vtSymbol.lower() == order.vtSymbol.lower()

            # 如果发生了成交
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount += 1            # 成交编号自增1

                tradeID = str(self.tradeCount)
                trade = tradeClass()
                trade.target_symbol = order.target_symbol
                trade.tradeID = tradeID
                trade.HaTradeID = tradeID
                trade.orderID = order.orderID
                trade.HaOrderID = order.orderID
                trade.direction = order.direction
                #trade.offset = order.offset

                # 添加result,如果没有持仓，则说明是一个新的交易对的开始
                if self.pos==0:
                    self.result.open_time=self.dt

                # 以买入为例：
                # 1. 假设当根K线的OHLC分别为：100, 125, 90, 110
                # 2. 假设在上一根K线结束(也是当前K线开始)的时刻，策略发出的委托为限价105
                # 3. 则在实际中的成交价会是100而不是105，因为委托发出时市场的最优价格是100
                if buyCross:
                    trade.price = min(order.price, buyBestCrossPrice)
                    self.pos+=order.totalVolume

                    # 为了计算交易对结果
                    self.result.open_base.append(trade.price)
                    self.result.open_vol.append(order.totalVolume)

                else:
                    trade.price = max(order.price, sellBestCrossPrice)
                    self.pos-=order.totalVolume

                    self.result.close_base.append(trade.price)
                    self.result.close_vol.append(order.totalVolume)
                    # 卖出后仓位为0,说明是一个交易对的结束
                    if self.pos==0:
                        # TODO:为了防止报错在这里加了slippage，commis，pnl，以后在引擎中赋值即可删掉
                        self.result.commis=0.01
                        self.result.pnl=10.0
                        self.result.slippage=0.1

                        self.result.clsoe_time=self.dt
                        self.resultList.append(self.result)
                        self.restartResult(self.result)

                trade.volume = order.totalVolume
                trade.tradeTime = str(self.dt)
                trade.dt = self.dt
                self.strategy.onTrade(trade)
                self.tradeDict[tradeID] = trade

                # 推送委托数据
                order.tradedVolume = order.totalVolume
                order.status = u'全部成交'

                self.strategy.onOrder(order)

                # 从字典中删除该限价单
                try:
                    del self.workingLimitOrderDict[orderID]
                except Exception as ex:
                    #self.writeCtaError(u'crossLimitOrder exception:{},{}'.format(str(ex), traceback.format_exc()))
                    print("crossLimitOrder exception",ex)

    # 辅助函数
    def roundToPriceTick(self, price, priceTick=None):
        """取整价格到合约最小价格变动"""
        if not priceTick:
            priceTick = self.priceTick
        if not priceTick:
            return price

        newPrice = round(price/priceTick, 0) * priceTick
        return newPrice
    
    def restartResult(self,result):
        '''清洗函数'''
        pass
    
    def makebar(self):
        '''把tick生成bar'''
        if not self.tickList:
            self.first_tick_time=datetime.now()

        if self.counter_tick==60:
            bar=barClass()
            bar.open=self.tickList[0]
            bar.close=self.tickList[-1]
            bar.high=max(self.tickList)
            bar.low=min(self.tickList)
            bar.datetime=self.first_tick_time

            self.tickList=[]
            self.first_tick_time=None
            self.counter_tick=0      # 重新赋值，重新开始

        self.counter_tick+=1
    
    def newTick(self,ticks):
        '''新鲜滚热辣的tick'''
        for tick in ticks:
            self.tick.__dict__=tick
            self.crossLimitOrder()
            self.crossStopOrder()
            self.strategy(tick)
            self.last_tick=tick
            
    def run(self):
        '''启动函数'''
        self.__activate=True
        self.gateway.connect()
        if not self.subscrib_target_symbol:
            self.subscrib_target_symbol='btc_usdt'

        while self.__activate:
            tick=self.gateway.getTick(self.subscrib_target_symbol)
            self.eventEngine.register('makeBar',self.makebar())
            #self.strategy.next(tick)
